(ns thi.ng.geom.test.core.core
  #?(:cljs
     (:require-macros
      [cemerick.cljs.test :refer (is deftest with-test run-tests testing)]))
  (:require
   [thi.ng.math.core :as m :refer [*eps* HALF_PI QUARTER_PI delta=]]
   [thi.ng.geom.core :as g]
   [thi.ng.geom.vector :as v :refer [vec2 vec3 V2 V3 V2X V2Y V3X V3Y V3Z]]
   [thi.ng.geom.matrix :as mat :refer [M32 M44 matrix32 matrix44]]
   [thi.ng.geom.quaternion :as q :refer [Q]]
   #?(:clj
      [clojure.test :refer :all]
      :cljs
      [cemerick.cljs.test])))

(def s 10.0)
(def ax 1.0) (def ay 2.0) (def az 3.0)
(def bx (* ax s)) (def by (* ay s)) (def bz (* az s))
(def a2 (vec2 ax ay)) (def a3 (vec3 ax ay az))
(def b2 (vec2 bx by)) (def b3 (vec3 bx by bz))

(def av2 [ax ay]) (def av3 [ax ay az])
(def bv2 [bx by]) (def bv3 [bx by bz])

(defn context-vec2-op
  [label op cop default]
  (testing
      (str "op: " label)
    (is (delta= [(cop ax bx) (cop ay by)] (op a2 b2)) (str label " a2 b2"))
    (is (delta= [(cop ax s) (cop ay s)] (op a2 s)) (str label " a2 n"))
    (is (delta= (op a2 b2) (op a2 b3)) (str label " a2 b3"))
    (is (delta= (op a2 b2) (op a2 bx by)) (str label " a2 n m"))
    (is (delta= (op a2 b2) (op a2 [bx by])) (str label " a2 [n m]"))
    (is (delta= (op (op a2 b2) by) (op a2 b2 by)) (str label " a2 b2 m"))
    (is (delta= (op (op a2 b2) b2) (op a2 b2 b2)) (str label " a2 b2 b2"))
    (is (delta= (op (op a2 bx) b2) (op a2 bx b2)) (str label " a2 n b2"))
    ;;(is (= (op (op (op a2 b2) a2) b2) (op a2 [b2 a2 b2])) (str label " a2 [b2 a2 b2]"))
    (if (= cop /)
      (is #?(:clj
             (not (Double/isFinite ((op a2 [s]) :y)))
             :cljs
             (not (js/isFinite ((op a2 [s]) :y))))
          (str label " a2 [n] fail"))
      (is (delta= (op a2 s default) (op a2 [s])) (str label " a2 [n]")))))

(defn context-vec3-op
  [label op cop default]
  (testing
      (str "op: " label)
    (is (delta= [(cop ax bx) (cop ay by) (cop az bz)] (op a3 b3)) (str label " a3 b3"))
    (is (delta= [(cop ax s) (cop ay s) (cop az s)] (op a3 s)) (str label " a3 n"))
    (is (delta= (op (op a3 bx) by) (op a3 bx by)) (str label " a3 n m"))
    (is (delta= (op (op a3 b3) by) (op a3 b3 by)) (str label " a3 b3 n"))
    (is (delta= (op (op a3 b3) b3) (op a3 b3 b3)) (str label " a3 b3 b3"))
    (is (delta= (op (op a3 bx) b3) (op a3 bx b3)) (str label " a3 n b3"))
    (is (delta= (op a3 b3) (op a3 bx by bz)) (str label " a3 n m o"))
    (is (delta= (op a3 b3) (op a3 [bx by bz])) (str label " a3 [n m o]"))
    ;;(is (= (op (op (op a3 b3) by) bz) (op a3 b3 by bz)) (str label " a3 b3 n m"))
    ;;(is (= (op (op (op a3 b3) b3) bz) (op a3 b3 b3 bz)) (str label " a3 b3 b3 m"))
    ;;(is (= (op (op (op a3 b3) b3) a3) (op a3 b3 b3 a3)) (str label " a3 b3 b3 a3"))
    ;;(is (= (op (op (op a3 bx) by) b3) (op a3 bx by b3)) (str label " a3 n m b3"))
    ;;(is (= (op (op (op a3 b3) a3) b3) (op a3 [b3 a3 b3])) (str label " a3 [b3 a3 b3]"))
    (if (= cop /)
      (is #?(:clj
             (not (Double/isFinite ((op a3 [bx by]) :z)))
             :cljs
             (not (js/isFinite ((op a3 [bx by]) :z))))
          (str label " a3 [n m] fail"))
      (is (delta= (op a3 bx by default) (op a3 [bx by])) (str label " a3 [n m]")))))

(defn context-dual-op
  [label dop op1 op2]
  (testing
      (str "dual op: " label)
    (is (delta= (op2 (op1 a2 bx) by) (dop a2 bx by)) (str label " a2 n m"))
    (is (delta= (op2 (op1 a2 b2) bx) (dop a2 b2 bx)) (str label " a2 b2 n"))
    (is (delta= (op2 (op1 a2 bx) b2) (dop a2 bx b2)) (str label " a2 n b2"))
    (is (not (delta= (dop a2 bx b2) (dop a2 b2 bx))) (str label " a2 n b2 != a2 b2 n"))
    (is (delta= (op2 (op1 a2 b2) a2) (dop a2 b2 a2)) (str label " a2 b2 a2"))

    (is (delta= (op2 (op1 a3 bx) by) (dop a3 bx by)) (str label " a3 n m"))
    (is (delta= (op2 (op1 a3 b3) bx) (dop a3 b3 bx)) (str label " a3 b3 n"))
    (is (delta= (op2 (op1 a3 bx) b3) (dop a3 bx b3)) (str label " a3 n b3"))
    (is (not (delta= (dop a3 bx b3) (dop a3 b3 bx))) (str label " a3 n b3 != a3 b3 n"))
    (is (delta= (op2 (op1 a3 b3) a3) (dop a3 b3 a3)) (str label " a3 b3 a3"))))

(deftest vec2-ctor
  (is (v/vec2? (vec2 1)) "vec2?")
  (is (= [1.0 1.0] (vec2 1)) "uniform from single number")
  (is (= [1.0 2.0] (vec2 1 2)) "two numbers")
  (is (= [1.0 2.0] (vec2 [1 2])) "single 2-vector")
  (is (= [1.0 2.0] (vec2 [1 2 3])) "single 3-vector")
  (is (= [1.0 2.0] (vec2 {:x 1 :y 2})) "single :x/:y map")
  (is (= [1.0 0.0] (vec2 {:x 1})) "single :x map")
  (is (= [0.0 2.0] (vec2 {:y 2})) "single :y map")
  (is (= [0.0 0.0] (vec2 {})) "single empty map")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (vec2 "a")) "fail w/ str arg v2")
  ;; (is (thrown? #+clj ClassCastException #+cljs js/Error (vec2 ["a"])) "fail w/ [str] arg v2")
  ;; (is (thrown? #+clj ClassCastException #+cljs js/Error (vec2 {:x "a"})) "fail w/ str map v2")
  ;; (is (thrown? #+clj ClassCastException #+cljs js/Error (vec2 "a" "a")) "fail w/ str args v2")
  )

(deftest vec3-ctor
  (is (v/vec3? (vec3 1)) "vec3?")
  (is (= [1.0 1.0 1.0] (vec3 1)) "uniform from single number")
  (is (= [1.0 2.0 0.0] (vec3 1 2)) "2 numbers")
  (is (= [1.0 2.0 3.0] (vec3 1 2 3)) "3 numbers")
  (is (= [1.0 2.0 0.0] (vec3 [1 2])) "single 2-vector")
  (is (= [1.0 2.0 3.0] (vec3 [1 2 3])) "single 3-vector")
  (is (= [1.0 2.0 0.0] (vec3 {:x 1 :y 2})) "single :x :y map")
  (is (= [1.0 2.0 3.0] (vec3 {:x 1 :y 2 :z 3.0})) "single :x :y :z map")
  (is (= [1.0 0.0 0.0] (vec3 {:x 1})) "single :x map")
  (is (= [0.0 2.0 0.0] (vec3 {:y 2})) "single :y map")
  (is (= [0.0 0.0 3.0] (vec3 {:z 3})) "single :z map")
  (is (= [0.0 0.0 0.0] (vec3 {})) "single empty map")
  (is (= [1.0 2.0 3.0] (vec3 (vec2 1 2) 3)) "v3 from v2 + y")
  (is (= [1.0 2.0 3.0] (vec3 [1 2] 3)) "v3 from vec + y")
  (is (= [1.0 2.0 3.0] (vec3 {:x 1 :y 2} 3)) "v3 from map + y")
  (is (= [1.0 2.0 0.0] (vec3 1 2)) "v3 from xy")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (vec3 "a")) "fail w/ str arg v3")
  ;; (is (thrown? #+clj ClassCastException #+cljs js/Error (vec3 ["a"])) "fail w/ [str] arg v3")
  ;; (is (thrown? #+clj ClassCastException #+cljs js/Error (vec3 {:x "a"})) "fail w/ str map v3")
  ;; (is (thrown? #+clj ClassCastException #+cljs js/Error (vec3 "a" "a")) "fail w/ str args v3")
  )

(deftest swizzle-read
  (is (= ax (:x a2) (:x a3)) ":x")
  (is (= ay (:y a2) (:y a3)) ":y")
  (is (= az (:z a3)) ":z")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (:z a2)) ":z fail a2")
  (is (= (:xy a2) (:xy a3)) ":xy a2=a3")
  (is (= [ax ay] (:xy a2)) ":xy")
  (is (= (:yx a2) (:yx a3)) ":yx a2=a3")
  (is (= [ay ax] (:yx a2)) ":yx")
  (is (= (:yy a2) (:yy a3)) ":yy a2=a3")
  (is (= [ay ay] (:yy a2)) ":yy")
  (is (= [ay ay] (:yy a3)) ":yy")
  (is (= [ay az] (:yz a3)) ":yz")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (:xyz a2)) ":xyz fail a2")
  (is (= [ax ay az] (:xyz a3)) ":xyz a3")
  (is (= [az ax ay] (:zxy a3)) ":zxy a3")
  (is (= (a2 0) (a3 0)) "idx 0")
  (is (= (a2 1) (a3 1)) "idx 1")
  (is (= az (a3 2)) "idx 2")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (a2 2)) "idx 2 fail a2")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (a3 3)) "idx 3 fail a3"))

(deftest swizzle-write
  (is (= [bx ay] (assoc a2 :x bx)) ":x")
  (is (= [bx ay az] (assoc a3 :x bx)) ":x")
  (is (= [ax by] (assoc a2 :y by)) ":y")
  (is (= [ax by az] (assoc a3 :y by)) ":y")
  (is (= [ax ay bz] (assoc a3 :z bz)) ":z")
  (is (= [bx by] (assoc a2 :xy [bx by])) ":xy")
  (is (= [bx by az] (assoc a3 :xy [bx by])) ":xy")
  (is (= [by bx] (assoc a2 :yx [bx by])) ":yx")
  (is (= [by bx az] (assoc a3 :yx [bx by])) ":yx")
  (is (= [bx ay by] (assoc a3 :xz [bx by])) ":xz")
  (is (= [bx by bz] (assoc a3 :xyz [bx by bz])) ":xyz 3d")
  (is (= [by bz bx] (assoc a3 :zxy [bx by bz])) ":zxy 3d")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (assoc a2 :xz [bx by])) ":xz fail 2d")
  (is (= [by ay bx] (assoc a3 :zx [bx by])) ":zx")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (assoc a2 :zx [bx by])) ":zx fail 2d")
  (is (= [ax bx by] (assoc a3 :yz [bx by])) ":yz")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (assoc a2 :yz [bx by])) ":yz fail 2d")
  (is (= [ax by bx] (assoc a3 :zy [bx by])) ":zy")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (assoc a2 :zy [bx by])) ":zy fail 2d")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (assoc a2 :xyz [bx by bz])) ":xyz fail 2d"))

(deftest protocol-impls
  (is (= (seq a2) (seq av2)) "seq a2")
  (is (= (rseq a2) (rseq av2)) "rseq a2")
  (is #?(:clj (not (v/vec2? (rseq a2))) :cljs (v/vec2? (rseq a2))) "rseq a vec2?")
  (is (= (seq a3) (seq av3)) "seq a3")
  (is (= (rseq a3) (rseq av3)) "rseq a3")
  (is #?(:clj (not (v/vec3? (rseq a3))) :cljs (v/vec3? (rseq a3))) "rseq a vec3?")
  (is (= ax (first a2)) "first a2")
  (is (= ax (first a3)) "first a3")
  (is (= (rest a2) [ay]) "rest a2")
  (is (= (next a2) [ay]) "next a2")
  (is (nil? (nnext a2)) "nnext a2 nil?")
  (is (= (rest a3) (rest av3)) "rest a3")
  (is (= (next a3) (next av3)) "next a3")
  (is (= (nnext a3) [az]) "nnext a3")
  (is (nil? (next (nnext a2))) "nnext a3 nil?")
  (is (= ay (nth a2 1)) "nth a2")
  (is (= -1 (nth a2 2 -1)) "nth a2 default")
  #?(:clj
     (is (thrown? ArrayIndexOutOfBoundsException (nth a2 2)) "nth a2 fail")
     :cljs
     (is (thrown? js/Error (nth a2 2)) "nth a2 fail"))
  (is (= az (nth a3 2)) "nth a3")
  (is (= -1 (nth a3 3 -1)) "nth a3 default")
  #?(:clj
     (is (thrown? ArrayIndexOutOfBoundsException (nth a3 3)) "nth a3 fail")
     :cljs
     (is (thrown? js/Error (nth a3 3)) "nth a3 fail"))
  (is (= ay (peek a2)) "peek a2")
  (is (= az (peek a3)) "peek a3")
  (is (= (pop a2) [ax]) "pop a2")
  (is (= (pop a3) a2) "pop a3")
  (is (= (type (pop a2)) #?(:clj clojure.lang.PersistentVector :cljs cljs.core.PersistentVector)) "pop a2 type")
  (is (v/vec2? (pop a3)) "pop a3 vec2?")
  (is (v/vec3? (conj a2 3)) "conj a2")
  (is (= a3 (conj a2 3)) "conj a2 = a3")
  (is (instance? #?(:clj clojure.lang.PersistentVector :cljs cljs.core.PersistentVector) (conj a3 4)) "conj a3 PersistentVector?")
  (is (= [ax ay az 4] (conj a3 4)) "conj a3 = [1 2 3 4]")
  (is (let [[x y z] a2] (and (= a2 [x y]) (nil? z))) "destructure a2")
  (is (let [[x y z w] a3] (and (= a3 [x y z]) (nil? w))) "destructure a3")
  (is (every? #(contains? a2 %) [:x :y 0 1]) "contains keys a2")
  (is (every? #(contains? a3 %) [:x :y :z 0 1 2]) "contains keys a3")
  (is (== (hash [ax ay]) (hash a2)) "hash vec = a2")
  (is (== (hash [ax ay az]) (hash a3)) "hash vec = a3")
  (is (== (hash [1.0 2.0]) (hash (vec2 1 2))) "hash v2")
  (is (== (hash [1.0 2.0 3.0]) (hash (vec3 1 2 3))) "hash v3")
  #?@(:clj
      [(is (= (.hashCode [ax ay]) (.hashCode a2)) "hashCode vec = a2")
       (is (= (.hashCode [ax ay az]) (.hashCode a3)) "hashCode vec = a3")])
  (is (= {:foo 2} (meta (with-meta a2 {:foo 2}))) "meta v2")
  (is (= {:foo 3} (meta (with-meta a3 {:foo 3}))) "meta v3")
  (is (= [ax ay] a2) "vec = a2")
  (is (= (seq a2) a2) "seq = a2")
  (is (= [ax ay az] a3) "vec = a3")
  (is (= (seq a3) a3) "seq = a3")
  (is (not= a2 a3) "a2 != a3")
  (is (not= a3 a2) "a3 != a2")
  (is (not= a2 (rseq a2)) "a2 != rseq")
  (is (not= a3 (rseq a3)) "a3 != rseq")
  (is (not= a2 1) "a2 != x")
  (is (not= a3 1) "a3 != x")
  (is (= 1 (count (into #{} [a2 av2 a2]))) "#{a2 a2}")
  (is (= 1 (count (into #{} [a3 av3 a3]))) "#{a3 a3}")
  (is (= 2 (count a2)) "count a2")
  (is (= 3 (count a3)) "count a3")
  #?@(:clj
      [(is (= 2 (.size a2)) "size a2")
       (is (= 3 (.size a3)) "size a3")
       (is (= (seq av2) (iterator-seq (.iterator a2))) "iterator a2")
       (is (= (seq av3) (iterator-seq (.iterator a3))) "iterator a3")])
  (is (= ax (apply a2 [0])) "apply a2")
  (is (= ay (apply a2 [:y])) "apply a2 kw")
  (is (= [ay ax] (apply a2 [:yx])) "apply a2 swizzle")
  (is (= -1 (apply a2 [:z -1])) "apply a2 w/ default")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (apply a2 [:z])) "apply a2 fail")
  (is (= ax (apply a3 [0])) "apply a3")
  (is (= ay (apply a3 [:y])) "apply a3 kw")
  (is (= [ay ax az] (apply a3 [:yxz])) "apply a3 swizzle")
  (is (= -1 (apply a3 [:w -1])) "apply a3 w/ default")
  (is (thrown? #?(:clj IllegalArgumentException :cljs js/Error) (apply a3 [:w])) "apply a3 fail")
  (is (= 3.0 (reduce + a2)) "reduce a2")
  (is (= 6.0 (reduce + a3)) "reduce a3")
  (is (= 13.0 (reduce + 10 a2)) "reduce a2 w/ init")
  (is (= 16.0 (reduce + 10 a3)) "reduce a3 w/ init")
  (is (= -1 (reduce (fn [_ _] (reduced -1)) a2)) "reduce a2 w/ reduced")
  (is (= -1 (reduce (fn [_ _] (reduced -1)) a3)) "reduce a3 w/ reduced")
  (is (= -1 (reduce (fn [acc _] (reduced acc)) -1 a2)) "reduce a2 w/ reduced + init")
  (is (= -1 (reduce (fn [acc _] (reduced acc)) -1 a3)) "reduce a3 w/ reduced + init")
  (is (= #?(:clj "[1.0 2.0]" :cljs "[1 2]") (str a2)) "toString a2")
  (is (= #?(:clj "[1.0 2.0 3.0]" :cljs "[1 2 3]") (str a3)) "toString a3")
  (is (delta= a2 av2) "delta= a2 av2")
  (is (delta= a2 av2 1e-5) "delta= a2 av2 eps")
  (is (delta= a3 av3) "delta= a3 av3")
  (is (delta= a3 av3 1e-5) "delta= a3 av3 eps")
  (is (delta= [a2 b2] [av2 bv2]) "delta= [a2 b2]")
  (is (delta= [a3 b3] [av3 bv3]) "delta= [a3 b3]")
  (is (not (delta= [a2 b2] [a3 b3])) "not delta= [a2 b2]")
  (is (not (delta= a2 nil)) "not delta= a2 nil")
  (is (not (delta= a3 nil)) "not delta= a3 nil")
  (is (not (delta= a2 ax)) "not delta= a2 ax")
  (is (not (delta= a3 ax)) "not delta= a3 ax"))

(deftest vec2-math-ops
  (is (delta= [(+ ax) (+ ay)] (m/+ a2)) "+ a2")
  (is (delta= [(- ax) (- ay)] (m/- a2)) "- a2")
  (is (delta= [(* ax) (* ay)] (m/* a2)) "* a2")
  (is (delta= [(/ ax) (/ ay)] (m/div a2)) "/ a2")
  (is (delta= [(m/abs* ax) (m/abs* ay)] (m/abs (m/- a2))) "abs a2")
  (is (= V2 (g/clear* a2)) "clear a2")
  (context-vec2-op "+" m/+ + 0.0)
  (context-vec2-op "-" m/- - 0.0)
  (context-vec2-op "*" m/* * 0.0)
  (context-vec2-op "/" m/div / 0.0))

(deftest vec3-math-ops
  (is (delta= [(+ ax) (+ ay) (+ az)] (m/+ a3))) "+ a3"
  (is (delta= [(- ax) (- ay) (- az)] (m/- a3))) "- a3"
  (is (delta= [(* ax) (* ay) (* az)] (m/* a3))) "* a3"
  (is (delta= [(/ ax) (/ ay) (/ az)] (m/div a3))) "/ a3"
  (is (delta= [(m/abs* ax) (m/abs* ay) (m/abs* az)] (m/abs (m/- a3))) "abs a3")
  (is (= V3 (g/clear* a3)) "clear a3")
  (context-vec3-op "+" m/+ + 0.0)
  (context-vec3-op "-" m/- - 0.0)
  (context-vec3-op "*" m/* * 0.0)
  (context-vec3-op "/" m/div / 0.0))

(deftest dual-math-ops
  (context-dual-op "madd" m/madd m/* m/+)
  (context-dual-op "addm" m/addm m/+ m/*)
  (context-dual-op "msub" m/msub m/* m/-)
  (context-dual-op "subm" m/subm m/- m/*))

(deftest dotproduct
  (is (== (m/mag-squared a2) (m/dot a2 a2)) "dot a2 a2")
  (is (== 0 (m/dot a2 [(- ay) ax])) "dot 2d zero")
  (is (== (+ (* ax bx) (* ay by)) (m/dot a2 b2)) "dot a2 b2")
  (is (== (+ (* (- ax) bx) (* (- ay) by)) (m/dot (m/- a2) b2)) "dot -a2 b2")
  (is (== (m/mag-squared a3) (m/dot a3 a3)) "dot a3 a3")
  (is (== 0 (m/dot (vec3 1 0 0) (vec3 0 1 0))) "dot 3d xy zero")
  (is (== 0 (m/dot (vec3 1 0 0) (vec3 0 0 1))) "dot 3d xz zero")
  (is (== 0 (m/dot (vec3 0 1 0) (vec3 0 0 1))) "dot 3d yz zero")
  (is (== (+ (* ax bx) (* ay by) (* az bz)) (m/dot a3 b3)) "dot a3 b3")
  (is (== (+ (* (- ax) bx) (* (- ay) by) (* (- az) bz)) (m/dot (m/- a3) b3)) "dot -a3 b3"))

(deftest crossproduct
  (is (== 0 (m/cross V2X V2X)) "x cross x = 0")
  (is (== 1 (m/cross V2X V2Y)) "x cross y = 1")
  (is (= V3Z (m/cross V3X V3Y)) "+x cross +y = +z")
  (is (= V3Y (m/cross V3Z V3X)) "+z cross +x = +y")
  (is (= V3X (m/cross V3Y V3Z)) "+y cross +z = +x"))

(deftest mag
  (is (== (Math/sqrt (+ (* ax ax) (* ay ay))) (m/mag a2)) "mag a2")
  (is (== (Math/sqrt (+ (* ax ax) (* ay ay))) (m/mag (m/- a2))) "mag -a2")
  (is (== (+ (* ax ax) (* ay ay)) (m/mag-squared a2)) "mag-squared a2")
  (is (== (+ (* ax ax) (* ay ay)) (m/mag-squared (m/- a2))) "mag-squared -a2")
  (is (== (Math/sqrt (+ (* ax ax) (* ay ay) (* az az))) (m/mag a3)) "mag a3")
  (is (== (Math/sqrt (+ (* ax ax) (* ay ay) (* az az))) (m/mag (m/- a3))) "mag -a3")
  (is (== (+ (* ax ax) (* ay ay) (* az az)) (m/mag-squared a3)) "mag-squared a3")
  (is (== (+ (* ax ax) (* ay ay) (* az az)) (m/mag-squared (m/- a3))) "mag-squared -a3"))

(deftest normalize
  (is (delta= (let [m (m/mag a2)] [(/ ax m) (/ ay m)]) (m/normalize a2)) "norm a2")
  (is (delta= (let [m (/ s (m/mag a2))] [(* m ax) (* m ay)])
              (m/normalize a2 s)) "norm a2 s")
  (is (delta= s (m/mag (m/normalize a2 s))) "mag = norm a2 s")
  (is (delta= (let [m (m/mag a3)] [(/ ax m) (/ ay m) (/ az m)])
              (m/normalize a3)) "norm a3")
  (is (delta= (let [m (/ s (m/mag a3))] [(* m ax) (* m ay) (* m az)])
              (m/normalize a3 s)) "norm a3 s")
  (is (delta= s (m/mag (m/normalize a3 s))) "mag = norm a3 s"))

(deftest mix
  (is (delta= (m/mix (m/mix a2 b2 0.25) (m/mix b2 a2 0.25) 0.5)
              (m/mix a2 b2 b2 a2 0.25 0.5)) "mix bilinear2")
  (is (delta= (m/mix (m/mix a3 b3 0.25) (m/mix b3 a3 0.25) 0.5)
              (m/mix a3 b3 b3 a3 0.25 0.5)) "mix bilinear3"))

(deftest matrix-ops
  (let [m32a (matrix32 (range 1 7))  m32b (matrix32 (range 10 70 10))
        m44a (matrix44 (range 1 17)) m44b (matrix44 (range 10 170 10))]
    (is (== (hash (mapv double (range 1 7))) (hash m32a)) "hash M32")
    (is (== (hash (mapv double (range 1 17))) (hash m44a)) "hash M44")
    #?@(:clj
        [(is (== (.hashCode (mapv double (range 1 7))) (.hashCode m32a)) "hashCode M32")
         (is (== (.hashCode (mapv double (range 1 17))) (.hashCode m44a)) "hashCode M44")])
    (is (= {:foo 32} (meta (with-meta m32a {:foo 32}))) "meta M32")
    (is (= {:foo 44} (meta (with-meta m44a {:foo 44}))) "meta M44")
    (is (delta= V2Y (-> M32 (g/rotate HALF_PI) (g/transform-vector V2X))))
    (is (delta= V3Z (-> M44 (g/rotate-x HALF_PI) (g/transform-vector V3Y))) "rotx(+y) = +z")
    (is (delta= V3Z (-> M44 (g/rotate-around-axis V3X HALF_PI) (g/transform-vector V3Y))) "rot-axis-x(+y) = +z")
    (is (delta= V3X (-> M44 (g/rotate-y HALF_PI) (g/transform-vector V3Z))) "roty(+z) = +x")
    (is (delta= V3X (-> M44 (g/rotate-around-axis V3Y HALF_PI) (g/transform-vector V3Z))) "rot-axis-y(+z) = +x")
    (is (delta= V3Y (-> M44 (g/rotate-z HALF_PI) (g/transform-vector V3X))) "rotz(+x) = +y")
    (is (delta= V3Y (-> M44 (g/rotate-around-axis V3Z HALF_PI) (g/transform-vector V3X))) "rot-axis-z(+x) = +y")
    (is (delta= b2 (-> M32 (g/scale s) (g/transform-vector a2))) " a2 scale s")
    (is (delta= (m/* a2 b2) (-> M32 (g/scale b2) (g/transform-vector a2))) " a2 scale b2")
    (is (delta= b3 (-> M44 (g/scale s) (g/transform-vector a3))) " a3 scale s")
    (is (delta= (m/* a3 b3) (-> M44 (g/scale b3) (g/transform-vector a3))) " a3 scale b3")
    (is (delta= (m/+ a2 s) (-> M32 (g/translate s) (g/transform-vector a2))) " a2 trans s")
    (is (delta= (m/+ a2 b2) (-> M32 (g/translate b2) (g/transform-vector a2))) " a2 trans b2")
    (is (delta= (m/+ a3 s) (-> M44 (g/translate s) (g/transform-vector a3))) " a3 trans s")
    (is (delta= (m/+ a3 b3) (-> M44 (g/translate b3) (g/transform-vector a3))) " a3 trans b3")
    (is (delta= (-> V2X (g/scale s) (g/rotate QUARTER_PI) (g/translate b2))
                (-> M32 (g/translate b2) (g/rotate QUARTER_PI) (g/scale s) (g/transform-vector V2X)))
        "concat 2d")
    (is (delta= (-> V3X (g/scale s) (g/rotate-z QUARTER_PI) (g/translate b3))
                (-> M44 (g/translate b3) (g/rotate QUARTER_PI) (g/scale s) (g/transform-vector V3X)))
        "concat 3d")
    (is (delta= 1 (m/determinant M32)) "det M32 = 1")
    (is (delta= 1 (m/determinant (g/rotate M32 HALF_PI))) "det M32 rot = 1")
    (is (delta= 200 (m/determinant (g/scale M32 (vec2 10 20)))) "det M32 scaled")
    (is (delta= 1 (m/determinant M44)) "det M44 = 1")
    (is (delta= 1 (m/determinant (g/rotate-around-axis M44 (m/normalize (vec3 1)) HALF_PI))) "det M44 rot = 1")
    (is (delta= 6000 (m/determinant (g/scale M44 (vec3 10 20 30)))) "det M44 scaled")
    (is (delta= a2 (let [mat (-> M32 (g/translate b2) (g/rotate QUARTER_PI) (g/scale s))]
                     (-> mat (g/transform-vector a2) (g/transform (m/invert mat))))) "invert 2d")
    (is (delta= a3 (let [mat (-> M44 (g/translate b3) (g/rotate-z QUARTER_PI) (g/scale s))]
                     (-> mat (g/transform-vector a3) (g/transform (m/invert mat))))) "invert 3d")
    (is (delta= (m/+ m32a m32b) (range 11 77 11)) "+ M32")
    (is (delta= (m/- m32b m32a) (range 9 60 9)) "- M32")
    (is (delta= (m/+ m44a m44b) (range 11 180 11)) "+ M44")
    (is (delta= (m/- m44b m44a) (range 9 150 9)) "- M44")
    (is (delta= M32 [1 0 0 0 1 0]) "delta= M32")
    (is (delta= M32 [1 0 0 0 1 0] 1e-3) "delta= M32 eps")
    (is (delta= M32 M32) "delta= M32 M32")
    (is (not (delta= M32 nil)) "not delta= M32 nil")
    (is (not (delta= M32 1)) "not delta= M32 1")
    (is (delta= M44 [1 0 0 0  0 1 0 0  0 0 1 0  0 0 0 1]) "delta= M44")
    (is (delta= M44 [1 0 0 0  0 1 0 0  0 0 1 0  0 0 0 1] 1e-3) "delta= M44 eps")
    (is (delta= M44 M44) "delta= M44 M44")
    (is (not (delta= M44 nil)) "not delta= M44 nil")
    (is (not (delta= M44 1)) "not delta= M44 1")
    (is (delta= [1 0 0 0 1 0 2] (conj M32 2)) "conj M32")
    (is (delta= [1 0 0 0  0 1 0 0  0 0 1 0  0 0 0 1 2] (conj M44 2)) "conj M44")
    (is (instance? #?(:clj clojure.lang.PersistentVector :cljs cljs.core.PersistentVector) (conj M32 2)) "conj M32 PersistentVector?")
    (is (instance? #?(:clj clojure.lang.PersistentVector :cljs cljs.core.PersistentVector) (conj M44 2)) "conj M44 PersistentVector?")))

(defn quat-matrix-roundtrip
  [n]
  (testing
      (dotimes [i n]
        (let [q (q/quat-from-axis-angle (v/randvec3) (rand 6.28))
              q' (q/quat-from-matrix (g/as-matrix q))
              v (v/randvec3)]
          (is (m/delta= (g/transform-vector q v) (g/transform-vector q' v) 1e-4))))))

(deftest quat-ops
  (is (== (hash [1.0 2.0 3.0 4.0]) (hash (q/quat 1 2 3 4))) "hash Quat4")
  #?(:clj (is (== (.hashCode [1.0 2.0 3.0 4.0]) (.hashCode (q/quat 1 2 3 4))) "hashCode Quat4"))
  (is (delta= a3 (-> (q/quat-from-axis-angle V3X 0.0) (g/transform-vector a3))))
  (is (delta= a3 (-> (q/quat-from-axis-angle V3Y 0.0) (g/transform-vector a3))))
  (is (delta= a3 (-> (q/quat-from-axis-angle V3Z 0.0) (g/transform-vector a3))))
  (is (delta= (g/rotate-around-axis a3 (m/normalize (vec3 1)) HALF_PI)
              (-> (q/quat-from-axis-angle (m/normalize (vec3 1)) HALF_PI) (g/transform-vector a3)))
      "rot a3 = rot quat a3")
  (is (delta= (-> M44 (g/rotate-around-axis (m/normalize (vec3 1)) HALF_PI) (g/transform-vector a3))
              (-> (q/quat-from-axis-angle (m/normalize (vec3 1)) HALF_PI) (g/transform-vector a3)))
      "rot m44 a3 = rot quat a3")
  (is (delta= (g/rotate-x a3 HALF_PI)
              (-> (q/quat-from-euler :xyz HALF_PI 0.0 0.0) (g/transform-vector a3)))
      "rotx a3 = quat pitch a3")
  (is (delta= (g/rotate-y a3 HALF_PI)
              (-> (q/quat-from-euler :xyz 0.0 HALF_PI 0.0) (g/transform-vector a3)))
      "roty a3 = quat yaw a3")
  (is (delta= (g/rotate-z a3 HALF_PI)
              (-> (q/quat-from-euler :xyz 0.0 0.0 HALF_PI) (g/transform-vector a3)))
      "rotz a3 = quat roll a3")
  (is (delta= (g/rotate-around-axis a3 (m/normalize (vec3 1)) HALF_PI)
              (-> M44
                  (g/rotate-around-axis (m/normalize (vec3 1)) HALF_PI)
                  (q/quat-from-matrix)
                  (g/transform-vector a3))))
  (quat-matrix-roundtrip 1000)
  (is (delta= [V3X HALF_PI] (q/as-axis-angle (q/quat-from-axis-angle V3X HALF_PI))))
  (is (delta= [V3Y HALF_PI] (q/as-axis-angle (q/quat-from-axis-angle V3Y HALF_PI))))
  (is (delta= [V3Z HALF_PI] (q/as-axis-angle (q/quat-from-axis-angle V3Z HALF_PI))))
  (is (delta= Q [0 0 0 1]) "delta= Q")
  (is (delta= Q [0 0 0 1] 1e-3) "delta= Q eps")
  (is (delta= Q Q) "delta= Q")
  (is (not (delta= Q nil)) "not delta= Q nil")
  (is (not (delta= Q 1)) "not delta= Q 1")
  (is (instance? #?(:clj clojure.lang.PersistentVector :cljs cljs.core.PersistentVector) (conj Q 2)) "conj Q PersistentVector?"))
